package com.example.bookservice.serviceImpl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.bookservice.dto.AuthorBookDto;
import com.example.bookservice.dto.BooksDTO;
import com.example.bookservice.dto.RequestParamsDto;
import com.example.bookservice.entity.Book;
import com.example.bookservice.entity.Chapter;
import com.example.bookservice.mapper.BookMapper;
import com.example.bookservice.service.BookService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.bookservice.service.ChapterService;
import com.example.bookservice.utils.EsResponseUtils;
import com.example.bookservice.vo.PageResultVo;
import com.example.commom.enums.StatusCodeEnum;
import com.example.commom.exception.CustomException;
import com.example.commom.global.KafkaConstants;
import com.example.commom.page.PageVO;
import com.example.commom.utils.ESUtils;
import com.example.commom.utils.KafkaUtils;
import com.example.feign.result.AjaxResult;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.elasticsearch.search.suggest.SuggestBuilder;
import org.elasticsearch.search.suggest.SuggestBuilders;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author 软工2104-李家强-2100770248
 * @since 2023-09-20
 */
@Slf4j
@Service
public class BookServiceImpl extends ServiceImpl<BookMapper, Book> implements BookService {


    @Autowired
    private RestHighLevelClient client;

    @Autowired
    private KafkaUtils kafkaUtils;

    @Autowired
    private ChapterService chapterService;

    @Autowired
    private ESUtils esUtils;

    @Autowired
    private EsResponseUtils esResponseUtils;


    private void buildBasicQuery(RequestParamsDto params, SearchRequest request) {
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();

        // must: 关键字搜索
        String key = params.getKey();
        log.info(key);
        if (StringUtils.hasLength(key)) {
            boolQuery.must(QueryBuilders.matchQuery("all", key))
                     .must(QueryBuilders.termQuery("isAvailable", true));
        } else {
            // 搜索框为空
            boolQuery.must(QueryBuilders.matchAllQuery())
                    .must(QueryBuilders.termQuery("isAvailable", true));
        }
        // 4. 分数
        BigDecimal minPrice = params.getMinScore();
        BigDecimal maxPrice = params.getMaxScore();
        if (minPrice != null && maxPrice != null) {
            boolQuery.filter(QueryBuilders.rangeQuery("averageMark")
                    .gte(minPrice).lte(maxPrice));
        }
        request.source().query(boolQuery);
    }


    /**
     *  读者浏览全部图书
     */
    @Override
    public AjaxResult getAllBooks(BooksDTO booksDTO) {

        // 准备

        // 1. 准备 request
        SearchRequest request = new SearchRequest("book");

        // 2. 准备 DSL
        // 2.1 构建BoolQuery
        if (StringUtils.hasLength(booksDTO.getCategory())) {
            request.source().query(QueryBuilders.boolQuery()
                    .must(QueryBuilders.termQuery("category", booksDTO.getCategory()))
                    .must(QueryBuilders.termQuery("isAvailable", true)));
        } else {
            request.source().query(QueryBuilders.boolQuery()
                    .must(QueryBuilders.matchAllQuery())
                    .must(QueryBuilders.termQuery("isAvailable", true)));
        }


        // 2.2 分页
        Integer page = booksDTO.getPageNum();
        Integer size = booksDTO.getPageSize();
        if (page == null || page <= 0) {
            page = 1;
        }
        if (size == null || size <= 0) {
            size = 10;
        }
        if (size >= 50) {
            size = 50;
        }
        request.source().from((page - 1) * size).size(size);

        // 2.3 排序方式
        String type = booksDTO.getSort();
        if (StringUtils.hasLength(type)) {
            if (type.equals("1")) {
                // 按照 打分
                request.source().sort("averageMark", SortOrder.DESC);
            } else {
                request.source().sort("readeCount", SortOrder.DESC);
            }
        }

        // 3. 发送请求, 得到响应
        SearchResponse response;
        try {
            response = client.search(request, RequestOptions.DEFAULT);
            // 4. 解析响应

            System.out.println(response);

            return AjaxResult.success(esResponseUtils.handelResponse(response));

        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     *  按照书名, 作者名字查找书籍
     */
    @Override
    public PageResultVo<?> searchByAuthorOrBook(RequestParamsDto params) {

        // 1. 准备 request
        SearchRequest request = new SearchRequest("book");

        // 2. 准备 DSL
        // 2.1 构建BoolQuery
        buildBasicQuery(params, request);

        // 2.2 分页
        Integer page = params.getPage();
        Integer size = params.getSize();
        if (page == null || page <= 0) {
            page = 1;
        }
        if (size == null || size <= 0) {
            size = 10;
        }
        if (size >= 50) {
            size = 50;
        }
        request.source().from((page - 1) * size).size(size);

        // 2.3 排序方式
        String type = params.getSortBy();
        if (StringUtils.hasLength(type)) {
            if (type.equals("1")) {
                // 按照 打分
                request.source().sort("averageMark", SortOrder.DESC);
            } else {
                request.source().sort("readeCount", SortOrder.DESC);
            }
        }

        request.source().highlighter(new HighlightBuilder()
                .field("title")
                .field("author")
                .requireFieldMatch(false)
                .preTags("<em>")
                .postTags("</em>")
        );


        // 3. 发送请求, 得到响应
        SearchResponse response;
        try {
            response = client.search(request, RequestOptions.DEFAULT);
            // 4. 解析响应

            System.out.println(response);

            return esResponseUtils.handelResponse(response);

        } catch (IOException e) {
            throw new RuntimeException(e);
        }


    }

    /**
     * 删除书籍
     */
    @Override
    public AjaxResult deleteBookById(Long id) {
        // 删除章节
        LambdaQueryWrapper<Chapter> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(Chapter::getBookId, id);
        boolean remove = chapterService.remove(lambdaQueryWrapper);
        boolean flag = false;
        // 删除书籍
        if (remove) {
            flag = removeById(id);
        }

        if (flag) {
            // 向 kafka 发送消息, 删除 es
            kafkaUtils.sendKafkaMessageWithRetry(KafkaConstants.KAFKA_BOOK_DELETE_ES,KafkaConstants.KAFKA_BOOK_DELETE_ES,
                    (id.toString()).getBytes(StandardCharsets.UTF_8));

            return AjaxResult.success(StatusCodeEnum.OK.getCode(), StatusCodeEnum.OK.getMsg());
        } else {
            return AjaxResult.fail(StatusCodeEnum.FAILED.getCode(), StatusCodeEnum.FAILED.getMsg());
        }
    }


    /**
     *  删除章节
     */
    @Override
    public AjaxResult deleteChapterById(Long id) {

        boolean flag = chapterService.removeById(id);
        if (flag) {
            // 向 kafka 发送消息, 删除 es
            kafkaUtils.sendKafkaMessageWithRetry(KafkaConstants.KAFKA_CHAPTER_DELETE_ES,KafkaConstants.KAFKA_CHAPTER_DELETE_ES,
                    (id.toString()).getBytes(StandardCharsets.UTF_8));

            return AjaxResult.success(StatusCodeEnum.OK.getCode(), StatusCodeEnum.OK.getMsg());
        } else {
            return AjaxResult.fail(StatusCodeEnum.FAILED.getCode(), StatusCodeEnum.FAILED.getMsg());
        }
    }

    /**
     *  作者浏览全部审核通过/未通过的图书
     */
    public PageResultVo<?> getAuthorIsAvailableBooks(AuthorBookDto authorBookDto) {

        // 1. 准备 request
        SearchRequest request = new SearchRequest("book");

        // 2. 准备 DSL
        // 2.1 构建BoolQuery
        request.source().query(QueryBuilders.boolQuery()
                .must(QueryBuilders.termQuery("isAvailable",authorBookDto.getAvailable()))
                .must(QueryBuilders.termQuery("author", authorBookDto.getAuthor())));

        // 2.2 分页
        Integer page = authorBookDto.getPageNum();
        Integer size = authorBookDto.getPageSize();
        if (page == null || page <= 0) {
            page = 1;
        }
        if (size == null || size <= 0) {
            size = 10;
        }
        if (size >= 50) {
            size = 50;
        }
        request.source().from((page - 1) * size).size(size);

        // 3. 发送请求, 得到响应
        SearchResponse response;
        try {
            response = client.search(request, RequestOptions.DEFAULT);
            // 4. 解析响应

            System.out.println(response);

            return esResponseUtils.handelResponse(response);

        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     *  新增/修改书籍, 利用kafka来操作
     */
    @Override
    public AjaxResult insertOrUpdateBook(Book book, String username) {
        // 将消息发布到 topic (book-topic) 中:
        book.setAuthor(username);
        // 为了数据安全性, 将审核相关字段设为null
        book.setIsApproved(false);
        book.setIsAvailable(false);
        book.setReadeCount(null);
        book.setAverageMark(null);
        book.setPhoto(null);

        if (book.getBookId() == null) {
            // 新增
            book.setPublicationDate(LocalDateTime.now());
            book.setUpdateTime(LocalDateTime.now());

        } else {
            // 修改
            book.setUpdateTime(LocalDateTime.now());
            book.setPublicationDate(null);
        }

        String operate = book.getBookId() == null ? "新增" : "修改";

        // 插入数据库
        boolean flag = saveOrUpdate(book);
        // 手动提交偏移量
        if (flag) {
            log.info(LocalDateTime.now() + ": ID为: " +  book.getBookId() + " 的书籍: "
                    + book.getTitle() + " 的章节 " + operate + " 完成!");

            // 向kafka发送消息, 通知 es 更新
            kafkaUtils.sendKafkaMessageWithRetry(KafkaConstants.KAFKA_BOOK_UPDATE_INSERT_ES,
                    KafkaConstants.KAFKA_BOOK_UPDATE_INSERT_ES,
                    (book.getBookId()+"").getBytes(StandardCharsets.UTF_8));

            return AjaxResult.success(StatusCodeEnum.OK.getCode(), StatusCodeEnum.OK.getMsg());
        } else {
            throw new CustomException(StatusCodeEnum.FAILED.getMsg(), StatusCodeEnum.FAILED.getCode());
        }
    }

    /**
     *  新增/修改 书籍章节
     */
    public AjaxResult insertOrUpdateChapter(Long bookId, Chapter chapter) {

        chapter.setBookId(bookId);
        chapter.setIsApproved(false);
        chapter.setIsAvailable(false);

        if (chapter.getChapterId() != null) {
            // 修改操作
            chapter.setPublicationDate(null);
            chapter.setUpdateDate(LocalDateTime.now());
        } else {
            // 新增操作
            chapter.setPublicationDate(LocalDateTime.now());
            chapter.setUpdateDate(LocalDateTime.now());
        }

        String operate = chapter.getChapterId() == null ? "新增" : "修改";

        // 插入数据库
        boolean flag = chapterService.saveOrUpdate(chapter);
        // 手动提交偏移量
        if (flag) {
            log.info(LocalDateTime.now() + ": 所属ID为: " + chapter.getBookId() + " 书籍的章节: "
                    + chapter.getTitle() + " 的章节 " + operate + " 完成!");

            // 向 kafka 发送消息
            kafkaUtils.sendKafkaMessageWithRetry(KafkaConstants.KAFKA_CHAPTER_INSERT_UPDATE_ES,
                    KafkaConstants.KAFKA_CHAPTER_INSERT_UPDATE_ES,
                    (chapter.getChapterId().toString()).getBytes(StandardCharsets.UTF_8));

            return AjaxResult.success(StatusCodeEnum.OK.getCode(), StatusCodeEnum.OK.getMsg());
        } else {
            throw new CustomException(StatusCodeEnum.FAILED.getMsg(), StatusCodeEnum.FAILED.getCode());
        }

    }


    /**
     *  查看指定书籍的详情(获取书籍全部章节)
     */
    @Override
    public PageResultVo<?> getBookInfo(Long targetBook) {


        try {
            // 1. 准备 request
            SearchRequest request = new SearchRequest("chapter");

            // 2. 准备 DSL
            request.source().query(QueryBuilders.boolQuery()
                    .must(QueryBuilders.termQuery("bookId", targetBook))
                    .must(QueryBuilders.termQuery("isAvailable", true)));

            // 3. 发起请求
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);

            return esResponseUtils.handelChapterResponse(response);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

    }

    /**
     *  查看指定书籍指定章节的详情信息
     */
    public Chapter getChapterInfo( Long targetBook, Long targetChapter) {

        LambdaQueryWrapper<Chapter> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(Chapter::getBookId, targetBook)
                .eq(Chapter::getChapterId, targetChapter);

        return chapterService.getOne(lambdaQueryWrapper);
    }

    /**
     *  获取未审核的书籍
     */
    @Override
    public AjaxResult getApprovedBookFalse(Integer pageNum, Integer pageSize) {

        LambdaQueryWrapper<Book> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(Book::getIsApproved, false)
                .orderByDesc(Book::getUpdateTime);

        Page<Book> page = page(
                new Page<>(
                        pageNum, pageSize
                ),
                lambdaQueryWrapper
        );

        return AjaxResult.success(new PageVO<>(page));
    }

    /**
     *  获取未审核的章节列表
     */
    @Override
    public AjaxResult getApprovedChapterFalse(Integer pageNum, Integer pageSize) {

        LambdaQueryWrapper<Chapter> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(Chapter::getIsApproved, false)
                .orderByDesc(Chapter::getUpdateDate);

        Page<Chapter> page = chapterService.page(
                new Page<>(
                        pageNum, pageSize
                ),
                lambdaQueryWrapper
        );

        return AjaxResult.success(new PageVO<>(page));
    }

    /**
     * 书籍搜索框自动补全
     */
    @Override
    public AjaxResult suggestions(String prefix) {

        try {
            SearchRequest request = new SearchRequest("book");

            request.source().suggest(new SuggestBuilder().addSuggestion(
                    "bookSuggestion",
                    SuggestBuilders.completionSuggestion("suggestion")
                            .skipDuplicates(true)
                            .prefix(prefix)
                            .size(10)
            ));

            SearchResponse response = client.search(request, RequestOptions.DEFAULT);

            return esUtils.handleSuggestionResponse(response, "bookSuggestion");

        } catch (IOException e) {
            throw new CustomException(e.getMessage(), StatusCodeEnum.FAILED.getCode());
        }
    }

    /**
     *  章节搜索框自动补全
     */
    @Override
    public AjaxResult suggestionChapter(Long bookId, String prefix) {

        try {
            SearchRequest request = new SearchRequest("chapter");

            request.source().suggest(new SuggestBuilder().addSuggestion(
                    "chapterSuggestion",
                    SuggestBuilders.completionSuggestion("suggestion")
                            .skipDuplicates(true)
                            .prefix(prefix)
                            .size(10)
            ));

            SearchResponse response = client.search(request, RequestOptions.DEFAULT);

            return esUtils.handleSuggestionChapterResponse(response, "chapterSuggestion", bookId);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}
